package core.proto;



import static app.constants.CollabVizConstants.*;

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JEditorPane;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Highlighter;
import javax.swing.text.Highlighter.Highlight;
import javax.swing.text.JTextComponent;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.jdesktop.swingx.*;
import org.jdesktop.swingx.mapviewer.DefaultWaypointRenderer;
import org.jdesktop.swingx.mapviewer.GeoPosition;
import org.jdesktop.swingx.mapviewer.Waypoint;
import org.jdesktop.swingx.mapviewer.WaypointPainter;
import org.jdesktop.swingx.mapviewer.WaypointRenderer;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import prefuse.util.ui.UILib;
import app.constants.CollabVizConstants;

import core.proto.annotation.Annotation;
import core.proto.annotation.AnnotationFocusListener;
import core.proto.annotation.AnnotationManager;
import core.proto.annotation.AnnotationModel.AnnotationLocation;
import core.proto.annotation.CannotAddAnnotationException;
import core.proto.annotation.DocAnnotationModel;
import core.proto.annotation.DocAnnotationUI;
import core.proto.annotation.MapAnnotationModel;
import core.proto.annotation.MapAnnotationUI;
import core.proto.annotation.WaypointRemovalListener;
import core.proto.search.DocumentSearchDocumentListener;
import core.proto.search.QueryLoggerDocumentListener;
import core.proto.search.SearchBar;
import core.proto.timeline.SavantHeaderRenderer;
import core.proto.timeline.SavantTimeBarViewer;
import core.proto.timeline.ScaleControlPanel;
import core.proto.vis.VizDemoRadial;
import core.util.logging.UserLogWriter;
import core.util.logging.constants.Action;
import core.util.logging.constants.Location;
import de.jaret.util.date.Interval;
import de.jaret.util.date.JaretDate;
import de.jaret.util.ui.timebars.TimeBarMarkerImpl;
import de.jaret.util.ui.timebars.strategy.IIntervalSelectionStrategy;
import de.jaret.util.ui.timebars.swing.TimeBarViewer;
import de.jaret.util.ui.timebars.swing.renderer.DefaultTitleRenderer;

public class AllComponents extends JPanel  {

	private static String previousAbsPath = NO_DOC_OPEN_TAG;
	private static String currentAbsPath = NO_DOC_OPEN_TAG;

	// google map tells 739 Ellsworth Avenue, Pittsburgh, PA has lattiude: 40.33430	longitude: -79.60484
	private static GeoPosition DEFAULT_MAP_POSITION = new GeoPosition(40.33430, -79.60484);

	private static final boolean ROTATE_NODES = true;
	private static final boolean DO_NOT_ALLOW_DIVIDER_MOVEMENT = false;

	public static Color color_text_highlight = new Color(195,217,255);

	private static final long serialVersionUID = 1L;

	private static PaintDoc document;

	private static ClosableTabbedPane documentContainer;

	private JPanel timelineScaleControlPanel; 
	private static SavantTimeBarViewer timeline;	

	public static ArrayList<String> openTabs = new ArrayList<String>();

	private static JPanel curDocContainer;

	public static String getHtmlRootFolderPath() {
		return 	HTML_FILES_FOLDER;
	}

	static JPanel directory;


	private static JPanel viz = VizDemoRadial.demo(Util.getServerResourceAsStream(RADIAL_TREE_DATA_FILE), "name", Util.getVizWidth(), Util.getVizHeight());

	// Note: it appears that static JTabbedPanes are rendered differently 
	// on my windows machine than non-static JTabbedPanes. I like the 
	// static JTabbedPane appearance so let's keep that. 


	static JXMapKit jxMap;

	static JTabbedPane vizTabbedPane = new JTabbedPane();
	static JTabbedPane fileTreeTabbedPane = new JTabbedPane();

	//	static TimeLine timeline;
	static JScrollPane timelineScroll;

	private static JSplitPane dirDocSplitPane;
	private static JSplitPane timeLineSplitPane;
	private static JSplitPane vizNoteSplitPane;
	private static JSplitPane leftRightSplitPane;

	public static JSplitPane docVizSplitPane = new JSplitPane();

	private static String htmlFilePath;

	private static VizDemoRadial visual; 

	public static final int ALL_VIEW = 0;
	public static final int NO_VIZ_VIEW = 1;
	public static final int NO_NOTES_VIEW = 2;
	public static final int NO_VIZNOTES_VIEW = 3;

	private static Set<MapAnnotationUI> waypoints; 

	private static JLayeredPane mapLayeredPane;
	private static JLayeredPane timelineLayeredPane;

	public AllComponents( int selectedView, boolean screenCap) throws IOException {
		super();

		initSplitPanes();

		documentContainer = new ClosableTabbedPane();

		waypoints = new HashSet<MapAnnotationUI>();
		mapLayeredPane = new JLayeredPane();
		timelineLayeredPane = new JLayeredPane();
		timelineLayeredPane.setMinimumSize(new Dimension(Util.getTimelineWidth() / 2, Util.getTimelineHeight()));
		timelineScroll = makeTimeline();

		UILib.setPlatformLookAndFeel();

		UserLogWriter.setScreenCap(screenCap);

		directory = new FileTree(new File(getHtmlRootFolderPath()), 1);

		document = new PaintDoc(NO_DOC_OPEN_TAG);


		for (int i = 0; i < viz.getComponentCount(); i++) {
			if (viz.getComponent(i) instanceof VizDemoRadial) {
				visual = (VizDemoRadial) viz.getComponent(i);
			}
		}

		setSize(WINDOW_WIDTH, WINDOW_HEIGHT);

		setProperties(vizTabbedPane, Util.getVizWidth(), Util.getVizHeight());
		vizTabbedPane.addTab("Visualization", viz);

		setProperties(fileTreeTabbedPane, Util.getFileTreeWidth(), Util.getFileTreeHeight());
		fileTreeTabbedPane.addTab("Directory", directory);

		setProperties(timelineScroll, Util.getTimelineWidth(), Util.getTimelineHeight());


		jxMap = makeMapComponent();
		setProperties(jxMap, Util.getMapWidth(), Util.getMapHeight());

		PropertyChangeListener dividerMovedListener = new PropertyChangeListener(){
			private void doWordWrap (int width) {
				if (curDocContainer != null){
					int newWidth = width - fileTreeTabbedPane.getWidth() - 40;
					curDocContainer.setSize(newWidth, curDocContainer.getHeight());
				}
			}

			// TODOwei change to allow "word wrap" (w/e that means).
			public void propertyChange(PropertyChangeEvent ce){
				int width = 0;
				int height = 0;
				if (ce.getSource() == leftRightSplitPane){
					height = timelineScroll.getHeight();
					width = leftRightSplitPane.getDividerLocation();
					timelineScroll.setSize(width,  height);
					timelineScroll.revalidate();
					doWordWrap(width);
				}else if (ce.getSource() == timeLineSplitPane){
					width = timelineScroll.getWidth();
					height = Util.getAdjustedHeight() - timeLineSplitPane.getDividerLocation();
					timelineScroll.setSize(width,  height);
					timelineScroll.revalidate();
				}else if (ce.getSource() == dirDocSplitPane) {
					doWordWrap(width);
				}
			}
		};

		leftRightSplitPane.addPropertyChangeListener(dividerMovedListener);
		timeLineSplitPane.addPropertyChangeListener(dividerMovedListener);

		PropertyChangeListener mapResizeResponder = new PropertyChangeListener(){
			Dimension size; 
			{
				size = new Dimension(0,0);
			}
			public void propertyChange(PropertyChangeEvent ce){
				if (ce.getPropertyName().equals(JSplitPane.DIVIDER_LOCATION_PROPERTY)){
					if (ce.getSource() == vizNoteSplitPane){
						size.setSize(jxMap.getWidth(), Util.getAdjustedHeight() - vizNoteSplitPane.getDividerLocation());
						jxMap.setSize(size);
						jxMap.getMainMap().setSize(size);
						updateVisibilityOfWaypoints();
					} else if (ce.getSource() == leftRightSplitPane){
						size.setSize(Util.getAdjustedWidth() - leftRightSplitPane.getDividerLocation(), jxMap.getHeight());
						jxMap.setSize(size);
						jxMap.getMainMap().setSize(size);
						updateVisibilityOfWaypoints();
					}

				}
			}
		}; 

		// This allows resizing of the map when divider of split pane is dragged up or down. 
		vizNoteSplitPane.addPropertyChangeListener(mapResizeResponder);
		leftRightSplitPane.addPropertyChangeListener(mapResizeResponder);


		jxMap.getMainMap().setFocusable(false); // necessary to prevent popup from losing focus when mouse cursor moves onto the map area.
		mapLayeredPane.setFocusable(true);
		mapLayeredPane.add(jxMap, JLayeredPane.DEFAULT_LAYER);

		timelineLayeredPane.add(timelineScroll, JLayeredPane.DEFAULT_LAYER);


		setProperties(timelineLayeredPane, Util.getTimelineWidth(), Util.getTimelineHeight());

		setProperties(jxMap, Util.getMapWidth(), Util.getMapHeight());
		setProperties(mapLayeredPane, Util.getMapWidth(), Util.getMapHeight());
		setProperties(documentContainer, Util.getDocWidth(), Util.getDocHeight());

		document.setDragEnabled(true);
		document.setEditable(true);



		documentContainer.setBackground(Color.GRAY);
		ChangeListener changeListener = new ChangeListener() {
			public void stateChanged(ChangeEvent changeEvent) {
				ClosableTabbedPane tabbedPane = (ClosableTabbedPane) changeEvent.getSource();
				int index = tabbedPane.getSelectedIndex();
				if (index == -1){
					// No document open, set to the default tag. 
					saveCurrentPath(NO_DOC_OPEN_TAG);
					visual.setHasNoTabOpen(true);
					vizUnhide();
				}else{
					// Set the name of the currently open doc. 
					String absPath = openTabs.get(index);
					saveCurrentPath(absPath);
					getFileTree().expandTreeByPath(absPath, true);

				}
			}
		};
		documentContainer.addChangeListener(changeListener);

		if (selectedView == ALL_VIEW) {						
			splitScreen(vizNoteSplitPane, vizTabbedPane, mapLayeredPane, false, Util.getVizHeight());
		} else if (selectedView == NO_VIZ_VIEW) {
			JPanel gray = new JPanel();
			setProperties(gray, Util.getVizWidth(), Util.getVizHeight());
			splitScreen(vizNoteSplitPane, gray, mapLayeredPane, false, Util.getVizHeight());
		} else if (selectedView == NO_NOTES_VIEW) {
			JPanel gray = new JPanel();
			setProperties(gray, Util.getMapWidth(), Util.getMapHeight());
			splitScreen(vizNoteSplitPane, vizTabbedPane, gray, false, Util.getVizHeight());
		} else if (selectedView == NO_VIZNOTES_VIEW) {
			JPanel gray = new JPanel();
			setProperties(gray, Util.getVizWidth(), Util.getVizHeight() + Util.getMapHeight());
		}
		// left: file tree; right: doc pane


		splitScreen(dirDocSplitPane, fileTreeTabbedPane, documentContainer, true, Util.getFileTreeWidth());			
		//		splitScreen(timeLineSplitPane, dirDocSplitPane, timeline, false, Util.getFileTreeHeight());
		splitScreen(timeLineSplitPane, dirDocSplitPane, timelineLayeredPane, false, Util.getFileTreeHeight());

		splitScreen(leftRightSplitPane, timeLineSplitPane, vizNoteSplitPane, true, Util.getFileTreeWidth() + Util.getDocWidth());
		add(leftRightSplitPane);


		setUpUserLogMouseEnterListener();
		loadAnnotationsToMap();
		loadAnnotationsToTimeline();
		setVisible(true);
	}

	/**
	 * TODO change the logging here since we have updated the components in the UI areas. 
	 * 
	 */
	private void setUpUserLogMouseEnterListener(){
		long mouseMask = AWTEvent.MOUSE_EVENT_MASK;

		final Hashtable<JComponent,Location> locs = new Hashtable<JComponent,Location>();
		locs.put(vizTabbedPane, Location.Viz);
		locs.put(fileTreeTabbedPane, Location.Directory);
		locs.put(jxMap, Location.Notes);
		locs.put(favorites, Location.Favorites);
		locs.put(documentContainer, Location.Documents);

		Toolkit.getDefaultToolkit().addAWTEventListener( 

				new AWTEventListener()
				{
					Location currentLoc = null;

					public void eventDispatched(AWTEvent e)
					{
						if (e.getSource() instanceof JComponent){
							// Safe cast because we set the mask to only handle mouse events for this handler. 
							MouseEvent m = (MouseEvent) e;


							switch(m.getID()){
							// Track the mouse enter/exit from various areas. 
							case MouseEvent.MOUSE_ENTERED:
								for (Entry<JComponent, Location> ent : locs.entrySet()){
									// check if we entered one of the areas we want. 
									if (SwingUtilities.isDescendingFrom((JComponent) e.getSource(), ent.getKey())){
										// check if we are already inside this general area. 
										if (ent.getValue() != currentLoc){
											if (currentLoc != null){
												// This happens when we drag item across 
												// UI areas, and for some reason the mouse enter/exit
												// events don't get raised in this case. 
												// So we explicitly check for this and 
												// just put an entry for when we left the last UI area
												// (so we count the dragging time as time spent in 
												// the UI area where the drag started). 
												UserLogWriter.log2("",	currentLoc, Action.MOUSEEXIT);
											}

											currentLoc = ent.getValue();
											UserLogWriter.log2("", currentLoc, Action.MOUSEENTER);
										}
										break;
									}
								}
								break;
							case MouseEvent.MOUSE_EXITED:
								for (Entry<JComponent, Location> ent : locs.entrySet()){
									// check if we left the general area. 
									if (SwingUtilities.isDescendingFrom((JComponent) e.getSource(), ent.getKey())){
										// Get point in the coordinate system of the enclosing component we wanna check. 
										Point p = SwingUtilities.convertPoint((Component) e.getSource(), m.getPoint(), ent.getKey());

										if (! ent.getKey().getBounds().contains(p)){
											UserLogWriter.log2("", currentLoc, Action.MOUSEEXIT);
											currentLoc = null;	
										}
										break;
									}
								}
								break;
							default:
								break;
							}
						}
					}
				}, 
				mouseMask);
	}

	private JScrollPane makeTimeline(){
		JPanel container = new JPanel(new BorderLayout());

		timeline = new SavantTimeBarViewer(timelineLayeredPane, TIMELINE_START_DATE, TIMELINE_END_DATE);

		final int CTRL_PANEL_HEIGHT = 40;
		timeline.setPreferredSize(new Dimension(Util.getTimelineWidth(),Util.getTimelineHeight() - CTRL_PANEL_HEIGHT));


		timeline.setOptimizeScrolling(false);
		timeline.setUseUniformHeight(true);

		timeline.setTimeScalePosition(TimeBarViewer.TIMESCALE_POSITION_TOP);
		timeline.setYAxisWidth(100);

		// allow marker grabbing in the diagram area
		timeline.setMarkerDraggingInDiagramArea(true);

		timeline.setRegionRectEnable(false);

		timeline.setDrawRowGrid(false);


		timeline.setHeaderRenderer(new SavantHeaderRenderer());

		// set a name for the viewer and setup the default title renderer
		//		_tbv.setName("Header name goes here");
		//		_tbv.setTitleRenderer(new DefaultTitleRenderer()); // this thing renders bg graphics of header


		// selection strategy: shortest first
		timeline.getDelegate().setIntervalSelectionStrategy(new IIntervalSelectionStrategy() {
			public Interval selectInterval(List<Interval> intervals) {
				Interval result = null; 
				for (Interval interval : intervals) {
					if (result == null || interval.getSeconds()<result.getSeconds()) {
						result = interval;
					}
				}
				return result;
			}
		});

		container.add(timeline, BorderLayout.NORTH);

		this.timelineScaleControlPanel = new ScaleControlPanel(timeline, SECONDS_IN_A_DAY, CTRL_PANEL_HEIGHT);

		container.add(timelineScaleControlPanel, BorderLayout.SOUTH);

		return new JScrollPane(container);
	}

	public static String getHtmlFilePath () {
		return htmlFilePath;
	}

	/**
	 * Method to set the dimensions for the 5 areas. 
	 * @param p
	 * @param w
	 * @param h
	 */
	private void setProperties(JComponent p, int w, int h) {
		Dimension d = new Dimension(w, h);
		p.setPreferredSize(d);
		p.setSize(d);
		if (DO_NOT_ALLOW_DIVIDER_MOVEMENT){
			p.setMinimumSize(d);
		}



	}

	/**
	 * Creates the split panes that lays out the overall UI. 
	 */
	private static void initSplitPanes(){
		dirDocSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
		timeLineSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
		vizNoteSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
		leftRightSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);		
		dirDocSplitPane.setDividerSize(DIVIDER_SIZE); 
		timeLineSplitPane.setDividerSize(DIVIDER_SIZE); 
		vizNoteSplitPane.setDividerSize(DIVIDER_SIZE); 
		leftRightSplitPane.setDividerSize(DIVIDER_SIZE);
	}

	/**
	 * Sets up the splitpane with the components. 
	 * @param sp
	 * @param a
	 * @param b
	 * @param leftRight Whether to split left-right (true) or top-down (false). 
	 * @param dividerPos
	 */
	public void splitScreen(JSplitPane sp, Component a, Component b, boolean leftRight,
			int dividerPos) {
		if (! leftRight) {
			sp.setTopComponent(a);
			sp.setBottomComponent(b);
		} else {
			sp.setLeftComponent(a);
			sp.setRightComponent(b);
		}
		sp.setDividerLocation(dividerPos);
	}

	private JXMapKit makeMapComponent() {
		final JXMapKit mapKit = new JXMapKit();

		final JXMapViewer map = mapKit.getMainMap();

		MouseAdapter adapter = makeMapMouseAdapter(mapKit);
		mapKit.getMainMap().addMouseMotionListener(adapter);
		mapKit.getMainMap().addMouseListener(adapter);

		setupMapSliderListener(mapKit);
		setupMapZoomButtonListener(mapKit);


		// Taken and modified from: 
		// http://swingdepot.blogspot.com/2009/08/openstreet-maps-example-java-swings.html
		mapKit.setDefaultProvider(org.jdesktop.swingx.JXMapKit.DefaultProviders.OpenStreetMaps);
		mapKit.setAddressLocationShown(false);
		mapKit.setDataProviderCreditShown(false);
		mapKit.setCenterPosition(new GeoPosition(0,0));
		mapKit.setMiniMapVisible(false);
		mapKit.setZoomSliderVisible(true);
		mapKit.setZoomButtonsVisible(true);
		mapKit.setZoom(2);

		mapKit.setAddressLocation(DEFAULT_MAP_POSITION);		
		return mapKit;
	}

	/**
	 * This ties in the zooming in/out using slider with the refresh of the waypoints on screen. 
	 * @param mapKit
	 */
	private void setupMapSliderListener(JXMapKit mapKit){
		final JSlider slider = mapKit.getZoomSlider();
		slider.addChangeListener(new ChangeListener(){
			@Override	
			public void stateChanged(ChangeEvent e) {
				SwingUtilities.invokeLater(new Runnable(){
					public void run(){
						updateVisibilityOfWaypoints();
						jxMap.getMainMap().repaint();
					}
				});
			}
		});
	}

	/**
	 * This ties in zooming in/out using buttons on map with refresh of waypoints on screen. 
	 * @param mapKit
	 */
	private void setupMapZoomButtonListener(JXMapKit mapKit){
		MouseAdapter adapter = new MouseAdapter(){
			@Override
			public void mouseClicked(MouseEvent me){
				SwingUtilities.invokeLater(new Runnable(){
					public void run(){
						updateVisibilityOfWaypoints();
						jxMap.getMainMap().repaint();
					}
				});
			}
		};
		mapKit.getZoomInButton().addMouseListener(adapter);
		mapKit.getZoomOutButton().addMouseListener(adapter);
	}

	private MouseAdapter makeMapMouseAdapter(final JXMapKit mapKit){
		return new MapMouseAdapter(mapKit);
	}

	private class MapMouseAdapter extends MouseAdapter implements WaypointRemovalListener, AnnotationFocusListener {

		// We want the behavior that if user is editing an annotation, by clicking 
		// somewhere on the map, he or she will be dismiss the popup editor and the 
		// annotation being edited. We have already built into AnnotationUI to dismiss 
		// the popup editor when the editor loses mouse focus. It either loses focus before
		// the mouse click event is triggered or after we handle the mouse click event. 
		// Therefore, we have the 3 states below. Using them allow us to pinpoint when 
		// we should interpret the mouse click as adding new waypoint, and when we should 
		// ignore it as the click to dismiss a popup editor. 
		final byte FINISHED_EDITING_PENDING = 1;
		final byte NO_WAYPOINT_BEING_EDITED = 2;
		final byte EDITING = 3;

		JXMapKit mapKit;

		byte editStatus = NO_WAYPOINT_BEING_EDITED; 

		public MapMouseAdapter(JXMapKit mapKit) {
			this.mapKit = mapKit;
		}

		@Override
		public void waypointRemoved(MapAnnotationUI wp) {
			waypoints.remove(wp);			
		}

		Rectangle rect = new Rectangle();

		/**
		 * This method handles the logic for hiding annotations when we drag the map and 
		 * the waypoint moves beyond the visible area. 
		 */
		public void mouseDragged(MouseEvent me){
			updateVisibilityOfWaypoints();
		}



		/**
		 * Handles adding of waypoint. 
		 */
		public void mouseClicked(MouseEvent me){

			// two cases. 
			// case 1: no existing waypoint clicked -> add new.  
			// case 2: click on area of existing waypoint -> edit.   
			if (me.getButton() == MouseEvent.BUTTON1){
				MapAnnotationUI closest = this.findWaypointAt(me.getPoint(), waypoints);

				if (editStatus == this.EDITING || editStatus == this.FINISHED_EDITING_PENDING) {
					this.editStatus = this.NO_WAYPOINT_BEING_EDITED;
					mapLayeredPane.requestFocusInWindow();
					return;
				}

				if (closest == null) {
					// case 1
					GeoPosition pos = mapKit.getMainMap().convertPointToGeoPosition(me.getPoint());
					String docKey = FilenameUtils.getBaseName(getCurrentPath());

					MapAnnotationModel model = new MapAnnotationModel("", docKey,pos,pos);

					MapAnnotationUI ui;
					try {
						ui = new MapAnnotationUI(model);
						Annotation anno = new Annotation(model, ui, false);
						waypoints.add(ui);
						ui.addToPane(mapLayeredPane);
						ui.show(me.getX(), me.getY());
						ui.addAnnotationFocusListener(this);
					} catch (CannotAddAnnotationException e) {
						Util.showCannotAddAnnotationPopup(mapLayeredPane);
					}
				} else {
					// case 2
					Point p = closest.getTextLabelLocation();
					closest.show((int) p.getX(), (int) p.getY());
				}
			}
		}			


		// Loop through waypoints and returns the first one that the user has clicked on, or null if none is clickedo n. 
		private MapAnnotationUI findWaypointAt(Point pos, Collection<MapAnnotationUI> wps) {
			for (MapAnnotationUI wp : wps) {
				if (wp.clickFallsIn(pos)){
					return wp;
				}
			}
			return null;
		}

		@Override
		public void lostFocus() {
			// This means the focus lost event is triggered before the mouse event. 
			if (this.editStatus == EDITING)
				editStatus = FINISHED_EDITING_PENDING;
		}

		@Override
		public void gainedFocus() {
			editStatus = this.EDITING;
		}


	}


	/**
	 * linear, go through waypoints, hide the annotations that moved out of viewport, and show annotations that moved into viewport. 
	 */
	private static void updateVisibilityOfWaypoints(){
		JXMapViewer map = jxMap.getMainMap();
		GeoPosition upperLeft = map.convertPointToGeoPosition(new Point(0, 0));
		GeoPosition lowerRight = map.convertPointToGeoPosition(new Point(map.getWidth(), map.getHeight()));

		for(MapAnnotationUI wp : waypoints){
			wp.recomputeVisibility (upperLeft, lowerRight, map);
		}
	}

	public static void fileTreeUpdateDoc(CaseNode node) throws IOException
	{

		// center the visualization tree to the node
		if (ROTATE_NODES) {
			visual.setHasNoTabOpen(false);
			rotateVisualizationTree(node.getName());
		}
		updateDoc(node.getAbsolutePath());
	}

	static void runVizAction (String action) {
		visual.runAction(action);
	}

	static void vizUnhide(){
		visual.unhide();
	}


	private static void rotateVisualizationTree(String nodeName) {
		visual.externalSetFocus(nodeName);
		viz.revalidate();
		viz.repaint();
	}


	private static String getTagValue(String sTag, Element eElement) {
		NodeList nlList = eElement.getElementsByTagName(sTag).item(0).getChildNodes();

		Node nValue = (Node) nlList.item(0);

		return nValue.getNodeValue();
	}

	private static Dimension getDimension(String absPath){
		try {
			// TODO don't read this EVERY time, it's inefficient. put in static initializer.
			DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
			org.w3c.dom.Document doc = dBuilder.parse(Util.getServerResource(DOC_SIZE_FILE).openStream());

			doc.getDocumentElement().normalize();

			NodeList nList = doc.getElementsByTagName("doc");


			for (int temp = 0; temp < nList.getLength(); temp++) {

				Node nNode = nList.item(temp);
				if (nNode.getNodeType() == Node.ELEMENT_NODE) {

					Element eElement = (Element) nNode;

					if(absPath.contains(getTagValue("path", eElement))){

						Dimension di = new Dimension(Integer.parseInt(getTagValue("width", eElement)), Integer.parseInt(getTagValue("height", eElement)));

						return di;
					}
				} 
			}
		}catch(ParserConfigurationException pce) {
			pce.printStackTrace();
		}catch(SAXException se) {
			se.printStackTrace();
		}catch(IOException ioe) {
			ioe.printStackTrace();
		}

		return null;
	}

	private static void loadAnnotationsToDoc(JLayeredPane desktop, String docKey) {
		for(Annotation anno : AnnotationManager.getAnnotations(docKey, AnnotationLocation.DOC)){
			anno.getView().addToPane(desktop);
			DocAnnotationModel m = (DocAnnotationModel) anno.getModel();
			anno.getView().shrink(m.getX(), m.getY());
		}
	}

	private static void loadAnnotationsToTimeline () {
		for (Annotation anno : AnnotationManager.getAllAnnotationsAt(AnnotationLocation.TIMELINE)){
			// Add to layered pane containing the map. 
			anno.getView().addToPane(timelineLayeredPane);
			// Add the annotation as an interval on the timeline. 
			timeline.externalAddAnnotation(anno);
		}
	}

	private static void loadAnnotationsToMap () {
		for (Annotation anno : AnnotationManager.getAllAnnotationsAt(AnnotationLocation.MAP)){
			// Add to list of waypoints. 
			waypoints.add((MapAnnotationUI)anno.getView());
			// Add to layered pane containing the map. 
			anno.getView().addToPane(mapLayeredPane);			
		}
		updateVisibilityOfWaypoints();
	}

	public static void updateDoc(final String absolutePath) throws IOException {

		File f = new File(absolutePath);
		htmlFilePath = absolutePath;

		if (openTabs.contains(absolutePath)) {
			int index = openTabs.indexOf(absolutePath);
			documentContainer.setSelectedIndex(index);
		} else {
			// Construct paint doc and add it to the UI hierarchy. 
			// Current hierarchy is like this:
			// JTabbledPane
			// --JSplitPane
			// ----JPanel (top half)
			// ------Highlight Button, Search Bar
			// ----JScrollPane
			// ------JDesktopPane
			// --------JPanel
			// ----------Margin area on left for annotations
			// ----------PaintDoc
			// --------User annotations

			JDesktopPane desktop = new JDesktopPane();
			desktop.setBackground(Color.white); // it's black by default which shows sometimes, using white hides it. 

			final PaintDoc doc = new PaintDoc(absolutePath);
			doc.setPage(f.toURI().toURL());
			doc.setDragEnabled(true);
			doc.addMouseMotionListener(new MouseAdapter(){
				public void mouseMoved(MouseEvent me){
					checkForHighlight(me, doc);
				}
			});
			doc.setEditable(false);


			// We use a separate container to hold the document because we want to be able to add a left margin 
			// so that annotations can go there. We could have modified the margin in the html documents instead,
			// but as of now some of the documents are created with MS word, which means they contain a ton of 
			// convoluted tags that are next to impossible to decipher. Following the KISS principle, it is far 
			// easier to just do this and add a left margin. Plus, we don't have to modify a whole bunch of html
			// documents each time we change our mind about the margin; we can just change the constant 1 time at 1 place.  
			final JPanel docContainer = new JPanel();

			curDocContainer = docContainer;

			docContainer.setFocusable(true);
			docContainer.addMouseListener(new MouseAdapter(){
				@Override
				public void mouseClicked(MouseEvent me){
					docContainer.requestFocusInWindow();
				}
			});
			docContainer.setLayout(new BorderLayout());
			JPanel blankArea = new JPanel();

			Dimension docSize = getDimension(absolutePath);
			//			final int docwidth = getDocWidth(absolutePath);
			//			int docheight = getDocHeight(absolutePath);
			docSize.setSize(docSize.getWidth() + ANNOTATION_MARGIN, docSize.getHeight());

			docContainer.add(doc, BorderLayout.CENTER);
			//			Dimension docSize = new Dimension(docwidth + ANNOTATION_MARGIN, docheight);

			blankArea.setPreferredSize(new Dimension(ANNOTATION_MARGIN, (int) docSize.getHeight()));
			blankArea.setBackground(Color.white);
			docContainer.add(blankArea, BorderLayout.WEST);

			docContainer.setBounds(0, 0, (int) docSize.getWidth(), (int) docSize.getHeight());

			desktop.add(docContainer);
			desktop.setPreferredSize(docSize);

			JSplitPane jsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
			jsp.setDividerLocation(50); 

			document = doc;
			SearchBar search = new SearchBar();
			search.addDocumentListener(new DocumentSearchDocumentListener(doc));
			search.addDocumentListener(new QueryLoggerDocumentListener(Location.Documents));
			search.addUserLogClickListener(Location.Documents);

			JPanel panel = new JPanel();
			panel.setLayout(new BorderLayout(0, 0));
			panel.add(search, BorderLayout.EAST);

			Box buttonBox = new Box(BoxLayout.X_AXIS);
			HighlightListener highListener = new HighlightListener(doc, desktop);

			loadAnnotationsToDoc(desktop, FilenameUtils.getBaseName(doc.docKey));

			JButton toggle = highListener.createToggle(); 
			buttonBox.add(toggle);
			panel.add(buttonBox, BorderLayout.WEST);
			doc.addCaretListener(highListener);

			JScrollPane scroll = new JScrollPane(desktop);
			jsp.setTopComponent(panel);
			jsp.setBottomComponent(scroll);

			// this is hackish because openTabs.add MUST be called 
			// before updating the tabbedPane, or the changedEvent 
			// handler in AllComponets doesn't work correctly
			// as it depends on tabbedPane and openTabs having 
			// corresponding elements.
			openTabs.add(absolutePath);

			String fileName = FilenameUtils.getBaseName(absolutePath);
			documentContainer.addTab(fileName.length() > MAX_TAB_NAME_LENGTH ? fileName.substring(0, MAX_TAB_NAME_LENGTH) : fileName, jsp);
			documentContainer.setSelectedIndex(openTabs.indexOf(absolutePath));
		}
	}

	private static void checkForHighlight(MouseEvent me, PaintDoc doc) {

		int loc = doc.viewToModel(me.getPoint());

		Highlight[] hilites = doc.getHighlighter().getHighlights();

		boolean annoFound = false;

		for(Highlight hilite: hilites){
			if( loc >= hilite.getStartOffset() && loc <= hilite.getEndOffset()){

				for(Annotation anno : AnnotationManager.getAnnotations(FilenameUtils.getBaseName(doc.docKey), AnnotationLocation.DOC)){
					DocAnnotationModel m = (DocAnnotationModel) anno.getModel();
					DocAnnotationUI j = (DocAnnotationUI) anno.getView();

					if(loc >= m.getStartIndex() && loc < m.getEndIndex()){
						j.changeToAltBackground();

						annoFound = true;
					}
					else{
						j.revertBackground();
					}

				}
			}
		}
		if(!annoFound){
			for(Annotation anno : AnnotationManager.getAnnotations(FilenameUtils.getBaseName(doc.docKey), AnnotationLocation.DOC)){
				DocAnnotationModel m = (DocAnnotationModel) anno.getModel();
				DocAnnotationUI j = (DocAnnotationUI) anno.getView();
				j.revertBackground();
			}
		}

	}


	private static int getDocWidth(String absPath){
		String name = FilenameUtils.getName(absPath).toLowerCase();
		if (name.contains("bus")){
			return 550;
		} else if (name.contains("interview")){
			return 1150;
		}
		return 1350;
	}

	private static int getDocHeight(String absPath){
		String name = FilenameUtils.getName(absPath).toLowerCase();
		if (name.contains("bus")){
			return 1300;
		}  else if (name.contains("report")){
			return 600;
		} else if (name.contains("interview")){
			return 600;
		}
		return 1100;
	}


	static GridLayout grid = new GridLayout(2, 1, 0, 0);

	private static JPanel favorites = new JPanel( /* grid */);

	private static JPanel saveBarPanel = new JPanel();

	private static JEditorPane saveTextPanel = new JEditorPane();

	JScrollPane saveScrollPanel = new JScrollPane(saveTextPanel);

	private static JProgressBar progressBar = null;

	public static void createProgressBar(String title) {
		progressBar = new JProgressBar(0, 10);
		progressBar.setStringPainted(true);
		Border border = BorderFactory.createTitledBorder(title);
		progressBar.setBorder(border);
		saveBarPanel.add(progressBar, BorderLayout.NORTH);
	}

	public static JProgressBar getProgressBar() {
		return progressBar;
	}

	public static JPanel getSaveBarPanel() {
		return saveBarPanel;
	}

	public static JEditorPane getSaveTextPanel() {
		return saveTextPanel;
	}




	public static void removeHighlights(JTextComponent textComp) {
		Highlighter hilite = textComp.getHighlighter();

		Highlighter.Highlight[] hilites = hilite.getHighlights();

		if (hilites.length == 0) {
			return;
		}

		for (int i = 0; i < hilites.length; i++) {
			hilite.removeHighlight(hilites[i]);
		}
		// added here for temp fix
		Component compoList[] = document.getComponents();
		if (compoList != null && compoList.length > 0) {
			for (Component compo : compoList) {
				if (compo instanceof JLabel) {
					compo.setEnabled(false);
					document.remove(compo);
				}
			}
		}
	}




	public static FileTree getFileTree() {
		return (FileTree) directory;
	}

	public static JEditorPane getDocumentPane() {
		return (JEditorPane) document;
	}
	public static PaintDoc getDocumentPaintDoc(){
		return document;
	}

	public static ClosableTabbedPane getMultipleDocs() {
		return documentContainer;
	}



	public static String getPreviousPath() {
		return previousAbsPath;
	}

	public static void saveCurrentPath(String absPath) {
		previousAbsPath = currentAbsPath;
		currentAbsPath = absPath;
	}

	/**
	 * Gets the absolute path of the currently open document. 
	 * @return
	 */
	public static String getCurrentPath() {
		return currentAbsPath;
	}

	public static JXMapViewer getMap() {
		return jxMap.getMainMap();
	}
}

